#include "kernel.h"
#include "config.h"
#include "globals.h"
#include "errno.h"

#include "util/debug.h"
#include "util/list.h"
#include "util/string.h"
#include "util/printf.h"

#include "proc/kthread.h"
#include "proc/proc.h"
#include "proc/sched.h"
#include "proc/proc.h"

#include "mm/slab.h"
#include "mm/page.h"
#include "mm/mmobj.h"
#include "mm/mm.h"
#include "mm/mman.h"

#include "vm/vmmap.h"

#include "fs/vfs.h"
#include "fs/vfs_syscall.h"
#include "fs/vnode.h"
#include "fs/file.h"

proc_t *curproc = NULL; /* global */
static slab_allocator_t *proc_allocator = NULL;

static list_t _proc_list;
static proc_t *proc_initproc = NULL; /* Pointer to the init process (PID 1) */

void
proc_init()
{
        list_init(&_proc_list);
        proc_allocator = slab_allocator_create("proc", sizeof(proc_t));
        KASSERT(proc_allocator != NULL);
}

static pid_t next_pid = 0;

/**
 * Returns the next available PID.
 *
 * Note: Where n is the number of running processes, this algorithm is
 * worst case O(n^2). As long as PIDs never wrap around it is O(n).
 *
 * @return the next available PID
 */
static int
_proc_getid()
{
        proc_t *p;
        pid_t pid = next_pid;
        while (1) {
failed:
                list_iterate_begin(&_proc_list, p, proc_t, p_list_link) {
                        if (p->p_pid == pid) {
                                if ((pid = (pid + 1) % PROC_MAX_COUNT) == next_pid) {
                                        return -1;
                                } else {
                                        goto failed;
                                }
                        }
                } list_iterate_end();
                next_pid = (pid + 1) % PROC_MAX_COUNT;
                return pid;
        }
}

/*
 * The new process, although it isn't really running since it has no
 * threads, should be in the PROC_RUNNING state.
 *
 * Don't forget to set proc_initproc when you create the init
 * process. You will need to be able to reference the init process
 * when reparenting processes to the init process.
 */
proc_t *
proc_create(char *name)
{

        /*NOT_YET_IMPLEMENTED("PROCS: proc_create"); */

       /*allocate memory using slab allocator*/
        proc_t* newProcess = (proc_t*)slab_obj_alloc(proc_allocator);
        if(newProcess==NULL)
        {
               panic("slab_obj_alloc failed\n");
               return NULL;
        }
        /* set all its values (including pid)*/
        int pid = _proc_getid();    
        if (-1 == pid)
        {
           panic("cannot get a new id\n");
           return NULL;
        }
        newProcess->p_pid = pid;
        strncpy(newProcess->p_comm, name, PROC_NAME_LEN);
        list_init(&newProcess->p_threads);
        list_init(&newProcess->p_children);

        /*if the _proc_getid can return 0 the first time call*/
         /*set parent's process id  ATTENTION: parent's id is just set to curproc ??
         is it possible that it's parent's id is equal to itself???*/
         /*KASSERT(0 != newID && "it is impossible that newID  == 0");*/




        if( 0 != pid)
        {
            newProcess->p_pproc = curproc;
#ifdef __VFS__

        newProcess->p_cwd = curproc->p_cwd;

        if (curproc->p_cwd)
            vref(curproc->p_cwd);
        int i;
        for (i = 0; i<NFILES; i++)
            newProcess->p_files[i] = fget(i);

#endif
        }
            
        else
        {
            newProcess->p_pproc = NULL;
#ifdef __VFS__
            /*set process0's file description table*/
        newProcess->p_cwd = NULL;
        memset(newProcess->p_files, NULL, NFILES*sizeof(file_t*));
#endif
            KASSERT(PID_IDLE != pid || list_empty(&_proc_list)); /* pid can only be PID_IDLE if this is the first process */
        }

         newProcess->p_status = 0;
         newProcess->p_state = PROC_RUNNING; /*???*/

         sched_queue_init(&newProcess->p_wait);
         newProcess->p_pagedir = (pagedir_t*)pt_create_pagedir();
         list_link_init(&newProcess->p_list_link);
         list_link_init(&newProcess->p_child_link);
         list_insert_tail(&_proc_list, &newProcess->p_list_link);

         if (NULL != newProcess->p_pproc)
         {
             list_insert_tail(&curproc->p_children, &newProcess->p_child_link);
         }
         
        /* insert it to the process list*/

        /* if the pid == 1, set it to the init process  ??? do it here??*/
         if( 1 == pid)
         {
            proc_initproc = newProcess;
            KASSERT(PID_INIT != pid || PID_IDLE == curproc->p_pid); /* pid can only be PID_INIT when creating from idle process */
         }
         
         /*memset(newProcess->p_files, 0, NFILES);*/

#ifdef __VM__
          newProcess->p_brk = NULL;
          newProcess->p_start_brk = NULL;
         newProcess->p_vmmap = vmmap_create();
         newProcess->p_vmmap->vmm_proc = newProcess;
#endif


         dbg_print("successfully create process %d\n", pid);
         return newProcess;

}

/**
 * Cleans up as much as the process as can be done from within the
 * process. This involves:
 *    - Closing all open files (VFS)
 *    - Cleaning up VM mappings (VM)
 *    - Waking up its parent if it is waiting
 *    - Reparenting any children to the init process
 *    - Setting its status and state appropriately
 *
 * The parent will finish destroying the process within do_waitpid (make
 * sure you understand why it cannot be done here). Until the parent
 * finishes destroying it, the process is informally called a 'zombie'
 * process.
 *
 * This is also where any children of the current process should be
 * reparented to the init process (unless, of course, the current
 * process is the init process. However, the init process should not
 * have any children at the time it exits).
 *
 * Note: You do _NOT_ have to special case the idle process. It should
 * never exit this way.
 *
 * @param status the status to exit the process with
 */
void
proc_cleanup(int status)
{
        KASSERT(NULL != proc_initproc); /* should have an "init" process */
        KASSERT(1 <= curproc->p_pid); /* this process should not be idle process */
        KASSERT(NULL != curproc->p_pproc); /* this process should have parent process */

        /*NOT_YET_IMPLEMENTED("PROCS: proc_cleanup");*/
    if(!sched_queue_empty(&curproc->p_pproc->p_wait))
        sched_broadcast_on(&curproc->p_pproc->p_wait);

    proc_t *p = NULL;
            list_iterate_begin(&curproc->p_children, p, proc_t, p_child_link) {
                          p->p_pproc = proc_initproc;   /*LU*/
                          list_remove(&p->p_child_link);
                          list_insert_tail(&proc_initproc->p_children, &p->p_child_link);
                }list_iterate_end();
                
    curproc->p_state = PROC_DEAD;
    curproc->p_status = status;


    int i;
    for (i = 0; i<NFILES; i++)
        do_close(i);
    if (curproc->p_cwd)
        vput(curproc->p_cwd);



}

/*
 * This has nothing to do with signals and kill(1).
 *
 * Calling this on the current process is equivalent to calling
 * do_exit().
 *
 * In Weenix, this is only called from proc_kill_all.
 */
/*
 * This has nothing to do with signals and kill(1).
 *
 * Calling this on the current process is equivalent to calling
 * do_exit().
 *
 * In Weenix, this is only called from proc_kill_all.
 */
void
proc_kill(proc_t *p, int status)
{
        /*NOT_YET_IMPLEMENTED("PROCS: proc_kill");*/
                        list_remove(&p->p_list_link);
                        list_remove(&p->p_child_link);
                        /*before We delete the process totally, do we need to free all thread here.  LU*/
                        kthread_t* p_child_thread = NULL;
                         dbg_print("\nFree the thread control blocks of %s\n", p->p_comm);
                         list_iterate_begin(&p->p_threads, p_child_thread, kthread_t, kt_plink) 
                         {     
                               kthread_destroy(p_child_thread);                             
                         } list_iterate_end();
                         dbg_print("\nFree the Process control block of %s\n", p->p_comm);
                        slab_obj_free(proc_allocator,p);
}
/*
 * Remember, proc_kill on the current process will _NOT_ return.
 * Don't kill direct children of the idle process.
 *
 * In Weenix, this is only called by sys_halt.
 */


void
proc_kill_all()
{
    proc_t *p = NULL;
    list_iterate_begin(&_proc_list, p, proc_t, p_list_link) {
            if(p->p_pid != PID_IDLE && p != curproc && p->p_pproc->p_pid != PID_IDLE){
                proc_kill(p, 0);
            }
    } list_iterate_end();
    proc_kill(curproc, 0);

/*    list_link_t *link = NULL;
    proc_t *p = NULL;
    while(!list_empty(&_proc_list))
    {
        link = _proc_list.l_prev;
        p = list_item(link, proc_t, p_list_link);
        list_remove(link);
        proc_kill(p, 0);
    }*/
        /*NOT_YET_IMPLEMENTED("PROCS: proc_kill_all");*/
}

proc_t *
proc_lookup(int pid)
{
        proc_t *p;
        list_iterate_begin(&_proc_list, p, proc_t, p_list_link) {
                if (p->p_pid == pid) {
                        return p;
                }
        } list_iterate_end();
        return NULL;
}

list_t *
proc_list()
{
        return &_proc_list;
}

/*
 * This function is only called from kthread_exit.
 *
 * Unless you are implementing MTP, this just means that the process
 * needs to be cleaned up and a new thread needs to be scheduled to
 * run. If you are implementing MTP, a single thread exiting does not
 * necessarily mean that the process should be exited.
 */
void
proc_thread_exited(void *retval)
{
    /*curthr->kt_retval = retval;*/
    int status = 0;

    /* this is not necessary */
   /* kthread_destroy(curthr); */
 
    /*KASSERT(curproc's thread list is empty)*/
    proc_cleanup(status);
    sched_switch();    
    /*NOT_YET_IMPLEMENTED("PROCS: proc_thread_exited");*/

}

/* If pid is -1 dispose of one of the exited children of the current
 * process and return its exit status in the status argument, or if
 * all children of this process are still running, then this function
 * blocks on its own p_wait queue until one exits.
 *
 * If pid is greater than 0 and the given pid is a child of the
 * current process then wait for the given pid to exit and dispose
 * of it.
 *
 * If the current process has no children, or the given pid is not
 * a child of the current process return -ECHILD.
 *
 * Pids other than -1 and positive numbers are not supported.
 * Options other than 0 are not supported.
 */
pid_t
do_waitpid(pid_t pid, int options, int *status)
{
    /*NOT_YET_IMPLEMENTED("PROCS: do_waitpid");*/
    KASSERT(options == 0);
    KASSERT(pid == -1 || pid >0);
    proc_t *p,*tp;
    pid_t res;

    /*current process has no children*/
    if(list_empty(&curproc->p_children))
        return -ECHILD;

    if(pid == -1)
    {
      char buf[1024];
      proc_info(curproc, buf, 1023);
      dbg_print("%s\n", buf);


        while(1)
        {
                list_iterate_begin(&curproc->p_children, p, proc_t, p_child_link) {


                  proc_info(p, buf, 1023);
                  dbg_print("%s\n", buf);
                      if(PROC_DEAD == p->p_state)
                      {
                        if(status)
                            *status = p->p_status;
                        res = p->p_pid;
                        KASSERT(NULL != p); /*the process should not be NULL */
                        KASSERT(-1 == pid || p->p_pid == pid);/* should be able to find the process */
                        KASSERT(NULL != p->p_pagedir);/* this process should have pagedir */

                        /*I don't understand the meaning of the link_list_link here, so I delete them. LU*/
                        list_remove(&p->p_list_link);
                        list_remove(&p->p_child_link);


                        /*before We delete the process totally, do we need to free all thread here.  LU*/
                        kthread_t* p_child_thread = NULL;
                         dbg_print("\nFree the thread control blocks of %s\n", p->p_comm);
                         list_iterate_begin(&p->p_threads, p_child_thread, kthread_t, kt_plink) 
                         {     
                                 /* thr points to a thread to be destroied */ KASSERT(KT_EXITED == p_child_thread->kt_state);
                                kthread_destroy(p_child_thread);
                                                        
                         } list_iterate_end();
                         dbg_print("\nFree the Process control block of %s\n", p->p_comm);
                        slab_obj_free(proc_allocator,p);
                            
                        return res;
                      }
                }list_iterate_end();
                /* all of its children are still running, blocking on wait queue*/
                dbg_print("\nProcess: %s's threads are still running \n", curproc->p_comm);
                sched_sleep_on(&curproc->p_wait);
                    
        }
    }
    else{
            p = proc_lookup(pid);
            KASSERT(NULL != p); /*the process should not be NULL */
            KASSERT(-1 == pid || p->p_pid == pid);/* should be able to find the process */
            KASSERT(NULL != p->p_pagedir);/* this process should have pagedir */
            /*check whether p is child of curproc*/
            int find = 0;
            list_iterate_begin(&curproc->p_children, tp, proc_t, p_child_link)
            {
                      if(tp == p)
                        find = 1;
            }list_iterate_end();

            if(find == 0)
                return -ECHILD;

            /*if p is not dead, wait*/
            while(p->p_state != PROC_DEAD)
                sched_sleep_on(&curproc->p_wait); 
           
            if(status)
                *status = p->p_status;
            res = p->p_pid;


            if(list_link_is_linked(&p->p_list_link))
            {
              list_remove(&p->p_list_link);
            }

            if(list_link_is_linked(&p->p_child_link))
            {
              list_remove(&p->p_child_link);
            }


            /*before We delete the process totally, do we need to free all thread here.  LU*/
            kthread_t* p_child_thread = NULL;
             list_iterate_begin(&p->p_threads, p_child_thread, kthread_t, kt_plink) 
             {     
                /* thr points to a thread to be destroied */ KASSERT(KT_EXITED == p_child_thread->kt_state);
                    kthread_destroy(p_child_thread);
                                            
             } list_iterate_end();

            slab_obj_free(proc_allocator,p);
            dbg_print("\nbegin to shut down2!\n");   /* what's the meaning of this one?  LU*/
            return res;           
        }
}

/*
 * Cancel all threads, join with them, and exit from the current
 * thread.
 *
 * @param status the exit status of the process
 */
void
do_exit(int status)
{
        /*NOT_YET_IMPLEMENTED("PROCS: do_exit");*/
       void *retval = (void *)status;
       kthread_exit(retval);   
}

size_t
proc_info(const void *arg, char *buf, size_t osize)
{
        const proc_t *p = (proc_t *) arg;
        size_t size = osize;
        proc_t *child;

        KASSERT(NULL != p);
        KASSERT(NULL != buf);

        iprintf(&buf, &size, "pid:          %i\n", p->p_pid);
        iprintf(&buf, &size, "name:         %s\n", p->p_comm);
        if (NULL != p->p_pproc) {
                iprintf(&buf, &size, "parent:       %i (%s)\n",
                        p->p_pproc->p_pid, p->p_pproc->p_comm);
        } else {
                iprintf(&buf, &size, "parent:       -\n");
        }

#ifdef __MTP__
        int count = 0;
        kthread_t *kthr;
        list_iterate_begin(&p->p_threads, kthr, kthread_t, kt_plink) {
                ++count;
        } list_iterate_end();
        iprintf(&buf, &size, "thread count: %i\n", count);
#endif

        if (list_empty(&p->p_children)) {
                iprintf(&buf, &size, "children:     -\n");
        } else {
                iprintf(&buf, &size, "children:\n");
        }
        list_iterate_begin(&p->p_children, child, proc_t, p_child_link) {
                iprintf(&buf, &size, "     %i (%s)\n", child->p_pid, child->p_comm);
        } list_iterate_end();

        iprintf(&buf, &size, "status:       %i\n", p->p_status);
        iprintf(&buf, &size, "state:        %i\n", p->p_state);

#ifdef __VFS__
#ifdef __GETCWD__
        if (NULL != p->p_cwd) {
                char cwd[256];
                lookup_dirpath(p->p_cwd, cwd, sizeof(cwd));
                iprintf(&buf, &size, "cwd:          %-s\n", cwd);
        } else {
                iprintf(&buf, &size, "cwd:          -\n");
        }
#endif /* __GETCWD__ */
#endif

#ifdef __VM__
        iprintf(&buf, &size, "start brk:    0x%p\n", p->p_start_brk);
        iprintf(&buf, &size, "brk:          0x%p\n", p->p_brk);
#endif

        return size;
}

size_t
proc_list_info(const void *arg, char *buf, size_t osize)
{
        size_t size = osize;
        proc_t *p;

        KASSERT(NULL == arg);
        KASSERT(NULL != buf);

#if defined(__VFS__) && defined(__GETCWD__)
        iprintf(&buf, &size, "%5s %-13s %-18s %-s\n", "PID", "NAME", "PARENT", "CWD");
#else
        iprintf(&buf, &size, "%5s %-13s %-s\n", "PID", "NAME", "PARENT");
#endif

        list_iterate_begin(&_proc_list, p, proc_t, p_list_link) {
                char parent[64];
                if (NULL != p->p_pproc) {
                        snprintf(parent, sizeof(parent),
                                 "%3i (%s)", p->p_pproc->p_pid, p->p_pproc->p_comm);
                } else {
                        snprintf(parent, sizeof(parent), "  -");
                }

#if defined(__VFS__) && defined(__GETCWD__)
                if (NULL != p->p_cwd) {
                        char cwd[256];
                        lookup_dirpath(p->p_cwd, cwd, sizeof(cwd));
                        iprintf(&buf, &size, " %3i  %-13s %-18s %-s\n",
                                p->p_pid, p->p_comm, parent, cwd);
                } else {
                        iprintf(&buf, &size, " %3i  %-13s %-18s -\n",
                                p->p_pid, p->p_comm, parent);
                }
#else
                iprintf(&buf, &size, " %3i  %-13s %-s\n",
                        p->p_pid, p->p_comm, parent);
#endif
        } list_iterate_end();
        return size;
}
